06. Numpy-数组运算

本章概览:NumPy数组运算

NumPy的核心优势在于其强大的向量化运算能力。

  • 统计运算:最大值、最小值、均值、方差、标准差
  • 数学运算:算术运算、对数、指数、取整
  • 金融指标计算:收益率、波动率、夏普比率
  • 集合运算:交集、并集、差集
  • 比较运算与布尔索引:条件筛选
  • 排序与搜索:排序、极值定位、条件替换

向量化运算为何如此快速?

与Python循环相比,NumPy运算在底层使用优化的C/Fortran代码,可以提供数十倍甚至上百倍的性能提升。

  • 单指令多数据(SIMD):一条CPU指令同时处理多个数值
  • 连续内存布局:提高CPU缓存命中率
  • 底层C实现:避免Python解释器的逐行开销

在金融大数据处理中,向量化运算是必须掌握的核心技能。

⭐ 平台任务:股票统计量计算

Listing 1
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import numpy as np  # 导入NumPy数值计算库
# 已知数据
stock_p=np.array([4.98,5.02,4.95,4.91,4.98,4.92,4.88,4.92,4.88,4.82,4.85,4.89,4.91,4.86,4.84,4.92,5.01,5.04])
# 创建NumPy数组stock_return
stock_return=np.array([-0.7968,1.4141,0.8147,-1.4056,1.2195,0.8197,-0.813,0.8197,1.2448,-0.6186,-0.818,-0.4073,1.0288,0.4132,-1.626,-1.7964,-0.5952,-1.5625])
# 1. 计算股票的最高价并输出
p_max=stock_p.max()
print(f"中国建筑8月最高股价为{p_max}")  # 输出中国建筑8月最高股价为
# 2. 计算股票的平均涨跌幅,使用round()函数保留三位小数并输出
return_avg=stock_return.mean().round(3)
print(f"平均涨跌幅为{return_avg}")  # 输出平均涨跌幅为
# 3. 计算股票涨跌幅的方差和标准差,使用round()函数并保留3位小数
return_var=stock_return.var().round(3)
return_std=stock_return.std().round(3)  # 计算标准差并四舍五入
print(f"涨跌幅的方差{return_var},标准差{return_std}")  # 输出涨跌幅的方差
中国建筑8月最高股价为5.04
平均涨跌幅为-0.148
涨跌幅的方差1.169,标准差1.081

平台任务代码解析:统计函数

方法 功能 等价写法
.max() 最大值 np.max(arr)
.min() 最小值 np.min(arr)
.mean() 算术平均 np.mean(arr)
.var() 方差 np.var(arr)
.std() 标准差 np.std(arr)
.round(n) 四舍五入到n位 np.round(arr, n)

方差与标准差的金融含义

方差衡量数据偏离均值的程度:

\[ \large{ \text{Var} = \frac{1}{n} \sum_{i=1}^{n}(x_i - \bar{x})^2 } \]

标准差是方差的平方根,与原始数据同单位:

\[ \large{ \text{Std} = \sqrt{\text{Var}} } \]

  • 在金融中,标准差即波动率,是风险的核心度量指标
  • NumPy默认 ddof=0(总体方差),金融分析常用 ddof=1(样本方差)

多维数组统计:axis参数

axis参数决定沿哪个方向聚合计算:

  • axis=0:垂直方向(跨行),对每列计算
  • axis=1:水平方向(跨列),对每行计算
  • axis=None:对所有元素计算(默认)

金融场景对应

  • axis=0:多只股票同一时间的市场平均表现
  • axis=1:单只股票的时间序列特征

多维数组统计:代码演示

Listing 2
import numpy as np

# 3只股票 × 4个交易日的收益率矩阵
returns = np.array([
    [0.05, 0.03, -0.02, 0.04],  # 股票A
    [0.02, 0.01, 0.03, -0.01],  # 股票B
    [-0.01, 0.02, 0.01, 0.05]   # 股票C
])

# axis=0:跨行计算 → 每日市场平均收益率
daily_avg = returns.mean(axis=0)
print(f'每日平均收益率: {daily_avg}')

# axis=1:跨列计算 → 每只股票平均收益率
stock_avg = returns.mean(axis=1)
print(f'每只股票平均收益率: {stock_avg}')
每日平均收益率: [0.02       0.02       0.00666667 0.02666667]
每只股票平均收益率: [0.025  0.0125 0.0175]

累积收益率计算:cumsum

Listing 3
# 沿axis=1方向计算累积和
cumulative_returns = returns.cumsum(axis=1)
print(f'累积收益率:\n{cumulative_returns}')
# 例如股票A第3列 = 0.05 + 0.03 + (-0.02) = 0.06
累积收益率:
[[ 0.05  0.08  0.06  0.1 ]
 [ 0.02  0.03  0.06  0.05]
 [-0.01  0.01  0.02  0.07]]
  • cumsum(axis=1):沿行方向逐步累加
  • 金融含义:累积收益率反映持仓一段时间的总收益
  • 应用:评估投资策略的历史表现

算术运算:元素级运算

NumPy支持元素级的算术运算,这是向量化的核心。

运算符 NumPy函数 描述
+ np.add 加法
- np.subtract 减法
* np.multiply 乘法
/ np.divide 除法
** np.power 幂运算

两个数组形状必须相同,或满足广播规则

算术运算:加法与减法

Listing 4
import numpy as np

# 5个交易日的股价数据
prices = np.array([10, 20, 30, 40, 50])

# 加法:价格调整
adjustment = np.array([1, 2, 3, 4, 5])
new_prices = prices + adjustment
print(f'调整后价格: {new_prices}')

# 减法:相邻日价差
price_diff = prices[1:] - prices[:-1]
print(f'价格变化: {price_diff}')
调整后价格: [11 22 33 44 55]
价格变化: [10 10 10 10]
  • 加法应用:除权除息后的价格调整
  • 减法应用:计算价格动量、技术分析指标

算术运算:乘法与除法

Listing 5
# 乘法:计算持仓市值
shares = np.array([100, 200, 150])
position_value = prices[:3] * shares
print(f'持仓价值: {position_value}')

# 除法:计算简单收益率
# 公式:return = (P_t - P_{t-1}) / P_{t-1}
simple_returns = (prices[1:] - prices[:-1]) / prices[:-1]
print(f'简单收益率: {simple_returns}')
持仓价值: [1000 4000 4500]
简单收益率: [1.         0.5        0.33333333 0.25      ]
  • 乘法应用:持仓价值 = 股价 × 持仓数量
  • 除法应用:计算日收益率

幂运算:复利计算

Listing 6
# 计算1到5年的复利因子(年化收益率5%)
compound = (1 + 0.05) ** np.arange(1, 6)
print(f'5年复利因子: {compound}')
5年复利因子: [1.05       1.1025     1.157625   1.21550625 1.27628156]
  • 第1年末:本金变为 \(1.05\)
  • 第5年末:本金变为 \(1.2763\)
  • 应用:复利计算、未来价值预测、投资回报规划

数学函数:对数收益率

Listing 7
# 5个交易日的股价
prices = np.array([100, 105, 98, 102, 110])

# 对数收益率 = ln(P_t) - ln(P_{t-1})
log_returns = np.diff(np.log(prices))
print(f'对数收益率: {log_returns}')
对数收益率: [ 0.04879016 -0.06899287  0.04000533  0.07550755]

对数收益率公式:

\[ \large{ r_{log} = \ln\left(\frac{P_t}{P_{t-1}}\right) = \ln(P_t) - \ln(P_{t-1}) } \]

对数收益率的三大优势

  1. 时间可加性:多期收益率可直接相加

\[ \large{ r_{log,1 \to 3} = r_{log,1 \to 2} + r_{log,2 \to 3} } \]

  1. 对称性:正负收益对称(-50%和+50%的对数收益率绝对值相等)

  2. 正态性:更接近正态分布,便于统计建模

应用场景:连续复利模型、Black-Scholes期权定价

指数函数与波动率计算

Listing 8
# 4只股票的年化收益率
annual_returns = np.array([0.05, 0.06, 0.04, 0.07])

# 指数函数:连续复利计算5年累计收益率
cumulative_5yr = np.exp(annual_returns * 5) - 1
print(f'5年累计收益率: {cumulative_5yr}')

# 波动率计算
volatility = np.std(annual_returns)
variance = volatility ** 2
print(f'波动率: {volatility:.4f}')
print(f'方差: {variance:.4f}')
5年累计收益率: [0.28402542 0.34985881 0.22140276 0.41906755]
波动率: 0.0112
方差: 0.0001

取整函数

Listing 9
prices_float = np.array([100.56, 105.23, 98.87])

print(f'向下取整: {np.floor(prices_float)}')
print(f'向上取整: {np.ceil(prices_float)}')
print(f'四舍五入: {np.round(prices_float)}')
向下取整: [100. 105.  98.]
向上取整: [101. 106.  99.]
四舍五入: [101. 105.  99.]
函数 功能 金融应用
np.floor() 向下取整 保守估计所需资金
np.ceil() 向上取整 确保资金充足
np.round() 四舍五入 报价、结果展示

金融案例:股票风险收益分析

Listing 10
import numpy as np

# 中国建筑8月股价数据(18个交易日)
stock_p = np.array([4.98, 5.02, 4.95, 4.91, 4.98, 4.92, 4.88, 4.92,
                    4.88, 4.82, 4.85, 4.89, 4.91, 4.86, 4.84, 4.92,
                    5.01, 5.04])

# 计算日收益率
daily_returns = (stock_p[1:] - stock_p[:-1]) / stock_p[:-1]

# 基本统计量
mean_return = daily_returns.mean()
std_return = daily_returns.std()
# 年化波动率 = 日波动率 × √252
annualized_vol = std_return * np.sqrt(252)

print('=' * 50)
print('中国建筑8月风险收益分析')
print('=' * 50)
print(f'日平均收益率: {mean_return:.4%}')
print(f'日收益率标准差: {std_return:.4%}')
print(f'年化波动率: {annualized_vol:.4%}')
==================================================
中国建筑8月风险收益分析
==================================================
日平均收益率: 0.0761%
日收益率标准差: 1.0575%
年化波动率: 16.7876%

风险收益分析(续):价格与下行风险

Listing 11
# 价格统计
max_price = stock_p.max()
min_price = stock_p.min()
price_range = max_price - min_price
print(f'最高价: {max_price:.2f}元')
print(f'最低价: {min_price:.2f}元')
print(f'价格区间: {price_range:.2f}元')

# 下行风险:只考虑下跌日
downside_returns = daily_returns[daily_returns < 0]
downside_risk = downside_returns.std() * np.sqrt(252)
print(f'下行波动率: {downside_risk:.4%}')

# 夏普比率(假设无风险利率3%)
sharpe_ratio = (mean_return * 252 - 0.03) / annualized_vol
print(f'夏普比率: {sharpe_ratio:.2f}')
最高价: 5.04元
最低价: 4.82元
价格区间: 0.22元
下行波动率: 4.6681%
夏普比率: 0.96

夏普比率的含义

夏普比率衡量每承担1单位风险获得的超额收益

\[ \large{ \text{Sharpe} = \frac{R_p - R_f}{\sigma_p} } \]

夏普比率 评价
> 1 优秀
0.5 ~ 1 良好
0 ~ 0.5 一般
< 0 较差(回报低于无风险利率)

集合运算:投资组合分析

Listing 12
import numpy as np

# 两个投资组合的持仓
portfolio_a = np.array(['600519.SH', '000858.SZ', '600036.SH', '000002.SZ'])
portfolio_b = np.array(['600036.SH', '601318.SH', '000858.SZ', '600000.SH'])

# 交集:共同持仓
common = np.intersect1d(portfolio_a, portfolio_b)
print(f'共同持仓: {common}')

# 并集:所有股票(去重)
all_stocks = np.union1d(portfolio_a, portfolio_b)
print(f'所有股票: {all_stocks}')

# 差集:独有持仓
unique_a = np.setdiff1d(portfolio_a, portfolio_b)
unique_b = np.setdiff1d(portfolio_b, portfolio_a)
print(f'A组合独有: {unique_a}')
print(f'B组合独有: {unique_b}')
共同持仓: ['000858.SZ' '600036.SH']
所有股票: ['000002.SZ' '000858.SZ' '600000.SH' '600036.SH' '600519.SH' '601318.SH']
A组合独有: ['000002.SZ' '600519.SH']
B组合独有: ['600000.SH' '601318.SH']

集合运算函数总结

函数 功能 金融应用
np.intersect1d 交集 找出共同持仓
np.union1d 并集 构建完整股票池
np.setdiff1d 差集 识别独有持仓
  • 投资组合重叠度分析:避免过度集中风险
  • 策略差异分析:了解不同投资经理的选股偏好

比较运算:生成布尔数组

Listing 13
import numpy as np

# 5只股票的价格
prices = np.array([10.5, 20.3, 15.8, 25.2, 18.6])

# 比较运算 → 返回布尔数组
above_20 = prices > 20
print(f'价格>20元: {above_20}')

# 布尔索引:提取满足条件的元素
expensive_stocks = prices[prices > 20]
print(f'高价股票: {expensive_stocks}')
价格>20元: [False  True False  True False]
高价股票: [20.3 25.2]
  • 比较运算符(>, <, >=, <=, ==, !=)返回布尔数组
  • 布尔数组可直接作为索引,提取 True 位置的元素

多条件筛选

Listing 14
# 筛选价格在15-25元之间的股票
filtered = prices[(prices >= 15) & (prices <= 25)]
print(f'价格在15-25元之间: {filtered}')
价格在15-25元之间: [20.3 15.8 18.6]

逻辑运算符

运算符 含义 说明
& 逻辑与(AND) 两个条件都满足
\| 逻辑或(OR) 至少一个条件满足
~ 逻辑非(NOT) 条件取反

注意:条件表达式必须加括号,因为位运算符优先级高于比较运算符。

排序:np.sort 与 argsort

  • np.sort():返回新数组,原数组不变
  • .sort()原地排序,直接修改原数组
  • np.argsort():返回排序后的索引

搜索极值与条件替换

Listing 16
# 搜索极值位置
max_idx = np.argmax(returns)
min_idx = np.argmin(returns)
print(f'最大值位置: {max_idx}, 值: {returns[max_idx]}')
print(f'最小值位置: {min_idx}, 值: {returns[min_idx]}')

# np.where:条件替换(三目运算)
adjusted = np.where(returns > 0, returns, 0)
print(f'负收益调整为0: {adjusted}')
最大值位置: 3, 值: 0.07
最小值位置: 1, 值: -0.02
负收益调整为0: [0.05 0.   0.03 0.07 0.   0.04]
  • np.argmax() / np.argmin():返回极值的索引位置
  • np.where(条件, True值, False值):条件替换
  • 金融应用:止损策略(限制亏损为0)

性能对比:循环 vs 向量化

Listing 17
import numpy as np
import time

n = 1000000
arr1 = np.random.randn(n)
arr2 = np.random.randn(n)

# Python循环
start = time.time()
result_slow = []
for i in range(n):
    result_slow.append(arr1[i] + arr2[i])
slow_time = time.time() - start

# NumPy向量化
start = time.time()
result_fast = arr1 + arr2
fast_time = time.time() - start

print(f'循环时间: {slow_time:.4f}秒')
print(f'向量化时间: {fast_time:.4f}秒')
print(f'加速比: {slow_time/fast_time:.1f}倍')
循环时间: 0.2197秒
向量化时间: 0.0013秒
加速比: 175.7倍

性能优化三条建议

  1. 使用向量化运算:避免Python for 循环,直接用NumPy运算符

  2. 就地操作节省内存

# 慢(创建新数组)
result = arr * 2
# 快(原地修改)
arr *= 2
  1. 预分配数组
# 慢(动态增长列表)
result = []
for i in range(n):
    result.append(i ** 2)
# 快(预分配空间)
result = np.empty(n)
for i in range(n):
    result[i] = i ** 2

本章总结

类别 核心函数 金融应用
统计运算 mean, std, var, max, min 收益率、波动率
数学函数 log, exp, sqrt, abs 对数收益率、复利
集合运算 intersect1d, union1d, setdiff1d 组合分析
比较与索引 >, <, &, \|, 布尔索引 条件筛选
排序与搜索 sort, argsort, argmax, where 排名、止损
性能优化 向量化、就地操作、预分配 大数据处理